<!--
 * 基于 wangEditor-next 的富文本编辑器组件二次封装
 * 版权所有 © 2021-present 有来开源组织
 *
 * 开源协议：https://opensource.org/licenses/MIT
 * 项目地址：https://gitee.com/youlaiorg/vue3-element-admin
 *
 * 在使用时，请保留此注释，感谢您对开源的支持！
 -->

<template>
  <div style="z-index: 999; border: 1px solid #ccc">
    <!-- 工具栏 -->
    <Toolbar
      :editor="editorRef"
      mode="simple"
      :default-config="toolbarConfig"
      style="border-bottom: 1px solid #ccc"
    />
    <!-- 编辑器 -->
    <Editor
      v-model="modelValue"
      :style="{ height: height, overflowY: 'hidden' }"
      :default-config="editorConfig"
      mode="simple"
      @on-created="handleCreated"
    />
  </div>

  <!-- 公式插入对话框 -->
  <el-dialog
    v-model="formulaDialogVisible"
    title="插入数学公式"
    width="820px"
    append-to-body
    destroy-on-close
    class="formula-dialog"
  >
    <el-scrollbar height="360px">
      <div v-for="category in formulaCategories" :key="category.title" class="symbol-category">
        <div class="symbol-category__title">{{ category.title }}</div>
        <div class="symbol-category__buttons">
          <el-button
            v-for="symbol in category.symbols"
            :key="symbol.name"
            size="small"
            type="primary"
            plain
            @click="insertSymbol(symbol.template)"
          >
            {{ symbol.label }}
          </el-button>
        </div>
      </div>
    </el-scrollbar>

    <div class="formula-input">
      <el-input
        v-model="formulaInput"
        type="textarea"
        :autosize="{ minRows: 3, maxRows: 6 }"
        placeholder="输入 LaTeX 公式，或通过上方按钮快速插入"
      />
      <div class="formula-preview">
        <div class="formula-preview__title">实时预览</div>
        <div class="formula-preview__content">
          <component :is="FormulaPreview" :value="formulaInput" />
        </div>
      </div>
    </div>

    <template #footer>
      <el-button @click="handleFormulaDialogClose">取 消</el-button>
      <el-button type="primary" @click="handleInsertFormula">插 入</el-button>
    </template>
  </el-dialog>
</template>

<script setup lang="ts">
import "@wangeditor-next/editor/dist/css/style.css";
import { defineAsyncComponent, nextTick, onBeforeUnmount, ref, shallowRef, watchEffect } from "vue";
import { Toolbar, Editor } from "@wangeditor-next/editor-for-vue";
import { IToolbarConfig, IEditorConfig, Boot } from "@wangeditor-next/editor";
import type { IDomEditor } from "@wangeditor-next/editor";
import FormulaModule from "@wangeditor-next/plugin-formula";
import { ElMessage } from "element-plus";

// 文件上传 API
import FileAPI from "@/api/file.api";

// 上传图片回调函数类型
type InsertFnType = (_url: string, _alt: string, _href: string) => void;

interface FormulaSymbol {
  label: string;
  name: string;
  template: string;
}

interface FormulaCategory {
  title: string;
  symbols: FormulaSymbol[];
}

const formulaCategories: FormulaCategory[] = [
  {
    title: "常用基本符号",
    symbols: [
      { label: "+", name: "plus", template: "+" },
      { label: "-", name: "minus", template: "-" },
      { label: "×", name: "times", template: "\\times" },
      { label: "÷", name: "div", template: "\\div" },
      { label: "=", name: "eq", template: "=" },
      { label: "≠", name: "neq", template: "\\neq" },
      { label: "≈", name: "approx", template: "\\approx" },
      { label: "≥", name: "geq", template: "\\geq" },
      { label: "≤", name: "leq", template: "\\leq" },
      { label: "∞", name: "infty", template: "\\infty" },
    ],
  },
  {
    title: "分数和根式",
    symbols: [
      { label: "分数", name: "frac", template: "\\frac{a}{b}" },
      { label: "平方根", name: "sqrt", template: "\\sqrt{x}" },
      { label: "n次根", name: "nsqrt", template: "\\sqrt[n]{x}" },
      { label: "绝对值", name: "abs", template: "\\left|x\\right|" },
      { label: "括号", name: "brackets", template: "\\left( x \\right)" },
      { label: "花括号", name: "curly", template: "\\left\\{ x \\right\\}" },
      { label: "中括号", name: "square", template: "\\left[ x \\right]" },
    ],
  },
  {
    title: "指数与下标",
    symbols: [
      { label: "上标", name: "sup", template: "x^{2}" },
      { label: "下标", name: "sub", template: "x_{i}" },
      { label: "上/下标", name: "supsub", template: "x_i^{n}" },
      { label: "指数函数", name: "exp", template: "e^{x}" },
      { label: "科学计数", name: "sci", template: "1.2\\times10^{3}" },
    ],
  },
  {
    title: "三角与反三角",
    symbols: [
      { label: "sin", name: "sin", template: "\\sin" },
      { label: "cos", name: "cos", template: "\\cos" },
      { label: "tan", name: "tan", template: "\\tan" },
      { label: "cot", name: "cot", template: "\\cot" },
      { label: "sec", name: "sec", template: "\\sec" },
      { label: "csc", name: "csc", template: "\\csc" },
      { label: "arcsin", name: "arcsin", template: "\\arcsin" },
      { label: "arctan", name: "arctan", template: "\\arctan" },
    ],
  },
  {
    title: "高等函数",
    symbols: [
      { label: "对数", name: "log", template: "\\log" },
      { label: "ln", name: "ln", template: "\\ln" },
      { label: "lim", name: "lim", template: "\\lim_{x \\to 0}" },
      { label: "exp", name: "exp", template: "\\exp" },
      { label: "max", name: "max", template: "\\max" },
      { label: "min", name: "min", template: "\\min" },
    ],
  },
  {
    title: "积分与求和",
    symbols: [
      { label: "∑", name: "sum", template: "\\sum_{i=1}^{n}" },
      { label: "∏", name: "prod", template: "\\prod_{i=1}^{n}" },
      { label: "∫", name: "int", template: "\\int_{a}^{b}" },
      { label: "∬", name: "iint", template: "\\iint" },
      { label: "∮", name: "oint", template: "\\oint" },
      { label: "微分", name: "diff", template: "\\mathrm{d}x" },
    ],
  },
  {
    title: "矩阵与向量",
    symbols: [
      { label: "矩阵", name: "matrix", template: "\\begin{bmatrix}a & b \\ c & d\\end{bmatrix}" },
      { label: "列向量", name: "vector", template: "\\begin{bmatrix}x \\ y\\end{bmatrix}" },
      { label: "行向量", name: "row", template: "\\begin{matrix}x & y\\end{matrix}" },
      { label: "单位矩阵", name: "identity", template: "\\mathbf{I}" },
      { label: "转置", name: "transpose", template: "A^{\\mathrm{T}}" },
    ],
  },
  {
    title: "集合与逻辑",
    symbols: [
      { label: "∈", name: "in", template: "\\in" },
      { label: "∉", name: "notin", template: "\\notin" },
      { label: "∪", name: "cup", template: "\\cup" },
      { label: "∩", name: "cap", template: "\\cap" },
      { label: "⊂", name: "subset", template: "\\subset" },
      { label: "∀", name: "forall", template: "\\forall" },
      { label: "∃", name: "exists", template: "\\exists" },
      { label: "⇒", name: "implies", template: "\\Rightarrow" },
    ],
  },
  {
    title: "希腊字母",
    symbols: [
      { label: "α", name: "alpha", template: "\\alpha" },
      { label: "β", name: "beta", template: "\\beta" },
      { label: "γ", name: "gamma", template: "\\gamma" },
      { label: "δ", name: "delta", template: "\\delta" },
      { label: "ε", name: "epsilon", template: "\\epsilon" },
      { label: "λ", name: "lambda", template: "\\lambda" },
      { label: "μ", name: "mu", template: "\\mu" },
      { label: "π", name: "pi", template: "\\pi" },
      { label: "θ", name: "theta", template: "\\theta" },
      { label: "Ω", name: "omega", template: "\\Omega" },
    ],
  },
  {
    title: "方程与条件",
    symbols: [
      {
        label: "方程组",
        name: "cases",
        template: "\\begin{cases} x + y = 1 \\ x - y = 0\\end{cases}",
      },
      {
        label: "分段函数",
        name: "piecewise",
        template: "f(x)=\\begin{cases}x^2 & x>0 \\ 0 & x \\le 0\\end{cases}",
      },
      { label: "定义", name: "define", template: ":=" },
      { label: "向量", name: "vec", template: "\\vec{v}" },
      { label: "模长", name: "norm", template: "\\lVert v \\rVert" },
      { label: "角度", name: "angle", template: "\\measuredangle ABC" },
    ],
  },
];

const FormulaPreview = defineAsyncComponent(() => import("./preview/FormulaPreview.vue"));

const props = defineProps({
  height: {
    type: String,
    default: "500px",
  },
  showInsertBlank: {
    type: Boolean,
    default: false,
  },
});

const modelValue = defineModel("modelValue", {
  type: String,
  required: false,
});

const editorRef = shallowRef<IDomEditor>();
const toolbarConfig = ref<Partial<IToolbarConfig>>({});
const editorConfig = ref<Partial<IEditorConfig>>({
  placeholder: "请输入内容...",
  MENU_CONF: {
    uploadImage: {
      customUpload(file: File, insertFn: InsertFnType) {
        FileAPI.uploadFile(file).then((res) => {
          insertFn(res.url, res.name, res.url);
        });
      },
    } as any,
  },
});

const formulaDialogVisible = ref(false);
const formulaInput = ref("");
const lastSelectedEditor = shallowRef<IDomEditor | null>(null);

class InsertBlankMenu {
  readonly title = "插入填空";
  readonly iconSvg =
    '<svg viewBox="0 0 1024 1024"><path d="M864 128H160c-17.7 0-32 14.3-32 32v704c0 17.7 14.3 32 32 32h704c17.7 0 32-14.3 32-32V160c0-17.7-14.3-32-32-32zM352 256h320c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H352c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8z m0 320h320c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H352c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8z m0 160h320c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H352c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8zM176 392c-31.2 0-56.7-25.4-56.7-56.7 0-31.2 25.4-56.7 56.7-56.7s56.7 25.4 56.7 56.7C232.7 366.6 207.2 392 176 392z m0 160c-31.2 0-56.7-25.4-56.7-56.7 0-31.2 25.4-56.7 56.7-56.7s56.7 25.4 56.7 56.7C232.7 526.6 207.2 552 176 552z m0 160c-31.2 0-56.7-25.4-56.7-56.7 0-31.2 25.4-56.7 56.7-56.7s56.7 25.4 56.7 56.7C232.7 686.6 207.2 712 176 712z"/></svg>';
  readonly tag = "button";

  getValue() {
    return "";
  }

  isActive() {
    return false;
  }

  isDisabled() {
    return false;
  }

  exec(editor: IDomEditor) {
    const html = editor.getHtml();
    const matches = html.match(/____(\d+)____/g) || [];
    const numbers = matches.map((m: string) => parseInt(m.match(/____(\d+)____/)![1]));
    const maxNum = numbers.length > 0 ? Math.max(...numbers) : 0;
    const nextNum = maxNum + 1;
    editor.insertText(`____${nextNum}____`);
  }
}

class FormulaDialogMenu {
  readonly title = "插入公式";
  readonly iconSvg =
    '<svg viewBox="0 0 1024 1024"><path d="M512 64 128 256v512l384 192 384-192V256L512 64Zm0 64 320 160v384L512 896 192 672V288l320-160Zm-64 160v64H384v64h64v256h128V416h64v-64h-64v-64H448Z"/></svg>';
  readonly tag = "button";

  getValue() {
    return "";
  }

  isActive() {
    return false;
  }

  isDisabled() {
    return false;
  }

  exec(editor: IDomEditor) {
    editor.focus();
    tryCallEditorMethod(editor, "saveSelection");
    lastSelectedEditor.value = editor;
    formulaDialogVisible.value = true;
  }
}

const BLANK_MENU_FLAG = "__custom_insert_blank_menu_registered__";
if (!(Boot as any)[BLANK_MENU_FLAG]) {
  Boot.registerMenu({
    key: "insertBlank",
    factory() {
      return new InsertBlankMenu();
    },
  });
  (Boot as any)[BLANK_MENU_FLAG] = true;
}

const FORMULA_MENU_FLAG = "__custom_formula_dialog_menu_registered__";
if (!(Boot as any)[FORMULA_MENU_FLAG]) {
  Boot.registerModule(FormulaModule);
  Boot.registerMenu({
    key: "formulaDialog",
    factory() {
      return new FormulaDialogMenu();
    },
  });
  (Boot as any)[FORMULA_MENU_FLAG] = true;
}

watchEffect(() => {
  const baseKeys = [
    "bold",
    "italic",
    "underline",
    "color",
    "bgColor",
    "fontSize",
    "fontFamily",
    "lineHeight",
    "bulletedList",
    "numberedList",
    "justifyLeft",
    "justifyCenter",
    "justifyRight",
    "insertImage",
    "insertTable",
    "codeBlock",
    "formulaDialog",
  ];

  if (props.showInsertBlank) {
    toolbarConfig.value = {
      toolbarKeys: [...baseKeys, "insertBlank"],
    };
  } else {
    toolbarConfig.value = {
      toolbarKeys: baseKeys,
    };
  }
});

const handleCreated = (editor: IDomEditor) => {
  editorRef.value = editor;
};

onBeforeUnmount(() => {
  const editor = editorRef.value;
  if (!editor) return;
  editor.destroy();
});

function insertSymbol(template: string) {
  formulaInput.value = `${formulaInput.value ? `${formulaInput.value} ` : ""}${template}`;
}

function handleFormulaDialogClose() {
  formulaDialogVisible.value = false;
  nextTick(() => {
    formulaInput.value = "";
  });
}

function handleInsertFormula() {
  const latex = formulaInput.value.trim();
  if (!latex) {
    ElMessage.warning("请输入公式或从列表中选择符号");
    return;
  }

  const editor = lastSelectedEditor.value ?? editorRef.value;
  if (!editor) {
    ElMessage.error("编辑器尚未就绪，请稍后重试");
    return;
  }

  tryCallEditorMethod(editor, "restoreSelection");
  editor.focus();
  if (typeof (editor as any).insertFormula === "function") {
    (editor as any).insertFormula(latex);
  } else {
    editor.insertText(`$${latex}$`);
  }

  handleFormulaDialogClose();
  ElMessage.success("公式已插入");
}

function tryCallEditorMethod(editor: IDomEditor, method: "saveSelection" | "restoreSelection") {
  const maybeEditor = editor as unknown as Record<string, unknown>;
  const fn = maybeEditor[method];
  if (typeof fn === "function") {
    (fn as () => void).call(editor);
  }
}
</script>

<style scoped lang="scss">
.formula-dialog {
  .el-dialog__body {
    padding: 12px 20px 0;
  }

  .symbol-category {
    margin-bottom: 16px;

    &__title {
      margin-bottom: 8px;
      font-weight: 600;
      color: #333;
    }

    &__buttons {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;

      .el-button {
        min-width: 72px;
      }
    }
  }

  .formula-input {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 12px;

    .el-textarea__inner {
      font-family: "Fira Code", Consolas, monospace;
    }
  }

  .formula-preview {
    padding: 10px;
    border: 1px dashed var(--el-border-color);
    border-radius: 4px;

    &__title {
      margin-bottom: 6px;
      font-size: 13px;
      color: #666;
    }

    &__content {
      min-height: 40px;
    }
  }
}
</style>
